/*=====================================================================*
 *                   Copyright (C) 2011 Paul Mineiro                   *
 * All rights reserved.                                                *
 *                                                                     *
 * Redistribution and use in source and binary forms, with             *
 * or without modification, are permitted provided that the            *
 * following conditions are met:                                       *
 *                                                                     *
 *     * Redistributions of source code must retain the                *
 *     above copyright notice, this list of conditions and             *
 *     the following disclaimer.                                       *
 *                                                                     *
 *     * Redistributions in binary form must reproduce the             *
 *     above copyright notice, this list of conditions and             *
 *     the following disclaimer in the documentation and/or            *
 *     other materials provided with the distribution.                 *
 *                                                                     *
 *     * Neither the name of Paul Mineiro nor the names                *
 *     of other contributors may be used to endorse or promote         *
 *     products derived from this software without specific            *
 *     prior written permission.                                       *
 *                                                                     *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND              *
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,         *
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES               *
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE             *
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER               *
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,                 *
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES            *
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE           *
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR                *
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF          *
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT           *
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY              *
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE             *
 * POSSIBILITY OF SUCH DAMAGE.                                         *
 *                                                                     *
 * Contact: Paul Mineiro <paul@mineiro.com>                            *
 *=====================================================================*/

#ifndef __SFT_REPORT_WORKER_HH__
#define __SFT_REPORT_WORKER_HH__

#include <boost/format.hpp>
#include <boost/optional.hpp>
#include <ostream>
#include "example.hh"
#include "loss.hh"
#include "threadqueue.hh"

namespace flassol
{
  void
  operator+= (std::vector<float>&         a,
              const std::vector<float>&   b)
    {
      for (unsigned int n = 0; n < std::min (a.size (), b.size ()); ++n)
        { 
          a[n] += b[n];
        }
    }

  class SftReportWorker
    {
      private:
        struct Stats
          { 
            boost::optional<uint64_t>     bad_lineno;
            uint64_t                      badlines;
            uint64_t                      goodlines;
            unsigned int                  cur_predict;
            boost::optional<unsigned int> cur_label;
            uint64_t                      cur_features;
            uint64_t                      example_count;
            float                         loss;
            float                         zo_loss;
            std::vector<float>            constant_loss;
            std::vector<float>            constant_zo_loss;
        
            explicit
            Stats (unsigned int k) : bad_lineno (boost::none),
                                     badlines (0),
                                     goodlines (0),
                                     cur_predict (0),
                                     cur_label (boost::none),
                                     cur_features (0),
                                     example_count (0),
                                     loss (0),
                                     zo_loss (0),
                                     constant_loss (k, 0),
                                     constant_zo_loss (k, 0)
              {
              }
        
            Stats&
            operator+= (const Stats& s)
              { 
                bad_lineno = s.bad_lineno;
                badlines += s.badlines;
                goodlines += s.goodlines;
                cur_predict = s.cur_predict;
                cur_label = s.cur_label;
                cur_features = s.cur_features;
                example_count += s.example_count;
                loss += s.loss;
                zo_loss += s.zo_loss;
                constant_loss += s.constant_loss;
                constant_zo_loss += s.constant_zo_loss;
        
                return *this;
              }

            void
            reset ()
              { 
                bad_lineno = boost::none;
                badlines = 0;
                goodlines = 0;
                cur_predict = 0;
                cur_label = boost::none;
                cur_features = 0;
                example_count = 0;
                loss = 0;
                zo_loss = 0;
                std::fill (constant_loss.begin (), constant_loss.end (), 0);
                std::fill (constant_zo_loss.begin (), constant_zo_loss.end (), 0);
              }
          };

        template<typename T>
        static boost::optional<unsigned int>
        argmin (const boost::optional<T>& x,
                unsigned int              k)
          {
            if (x)
              { 
                return argmin (*x, k);
              }
            else
              { 
                return boost::none;
              }
          }

        template<typename T>
        static unsigned int
        argmin (const T&     x,
                unsigned int k)
          {
            unsigned int rv = 0;
            
            for (unsigned int n = 1; n < k; ++n)
              {
                if (x[n] < x[rv])
                  { 
                    rv = n;
                  }
              }

            return rv;
          }

        static unsigned int
        argmax_zerozero (const EstimateResultVector& x,
                         unsigned int                k)
          {
            unsigned int rv = 0;
            float max = 0;

            for (unsigned int n = 1; n < k; ++n)
              { 
                if (x[n - 1].p > max)
                  { 
                    rv = n;
                    max = x[n - 1].p;
                  }
              }

            return rv;
          }

        void
        do_report (std::ostream&                  report,
                   const Stats&                   since_last,
                   const Stats&                   cumulative)
          {
            report << boost::format ("%-9s %-9s %-9s %-9s %9llu %9s %9f %8llu")
              % ((cumulative.example_count > 0)
                   ? (boost::format ("%-9.6f")
                        % (cumulative.loss / cumulative.example_count)
                     ).str ()
                   : "undefined"
                )
              % ((since_last.example_count > 0)
                   ? (boost::format ("%-9.6f")
                        % (since_last.loss / since_last.example_count)
                     ).str ()
                   : "undefined"
                )
              % ((cumulative.example_count > 0)
                   ? (boost::format ("%-9.6f")
                        % (cumulative.zo_loss / cumulative.example_count)
                     ).str ()
                   : "undefined"
                )
              % ((since_last.example_count > 0)
                   ? (boost::format ("%-9.6f")
                        % (since_last.zo_loss / since_last.example_count)
                     ).str ()
                   : "undefined"
                )
              % cumulative.example_count
              % (cumulative.cur_label
                    ? (boost::format ("%9f") % *cumulative.cur_label).str ()
                    : "unknown")
              % cumulative.cur_predict
              % cumulative.cur_features
            << std::endl;
          
            if (since_last.bad_lineno)
              { 
                report << since_last.badlines
                       << " bad lines encountered since last, latest at offset "
                       << *since_last.bad_lineno
                       << std::endl;
              }
          }

        CSMCQueue& in;
        unsigned int num_classes;
        boost::optional<std::ostream&> report;
        boost::optional<std::ostream&> predict;
        Loss& loss;

        uint64_t lineno;
        uint64_t output_number;
        Stats since_last;
        Stats cumulative;
        std::vector<std::vector<unsigned int> > confusion_matrix;

      public:
        SftReportWorker (CSMCQueue&                      _in,
                         unsigned int                    _num_classes,
                         boost::optional<std::ostream&>  _report,
                         boost::optional<std::ostream&>  _predict,
                         Loss&                           _loss)
          : in (_in),
            num_classes (_num_classes),
            report (_report),
            predict (_predict),
            loss (_loss),
            lineno (0),
            output_number (0),
            since_last (num_classes),
            cumulative (num_classes),
            confusion_matrix (num_classes, 
                              std::vector<unsigned int> (num_classes, 0))
          {
          }

        void operator() ()
          {
            std::vector<boost::optional<CSMCExample> >* rvec = 0;

            if (report)
              {  
                 *report <<
          "cumul     since     cumul     since       example  current   current  current"
                     << std::endl <<
          "loss      last      0/1 loss  last 0/1    counter    label   predict features"
                     << std::endl;
              }

            while (in.pop (rvec))
              {
                for (unsigned int n = 0; n < rvec->size (); ++n)
                  {
                    ++since_last.example_count;
                    ++lineno;

                    if (! (*rvec)[n])
                      {
                        since_last.bad_lineno = lineno;
                        ++since_last.badlines;
                      }
                    else
                      {
                        EstimateResultVector result (num_classes - 1);

                        ++since_last.goodlines;

                        for (unsigned int i = 0; 
                             i < (*rvec)[n]->initials->size ();
                             ++i)
                          {
                            for (unsigned int k = 0; k + 1 < num_classes; ++k)
                              {
                                result[k].p += (*(*rvec)[n]->initials)[i][k].p;
                                result[k].n += (*(*rvec)[n]->initials)[i][k].n;
                              }
                          }

                        since_last.cur_predict = argmax_zerozero (result,
                                                                  num_classes);
                        since_last.cur_label = argmin ((*rvec)[n]->cost,
                                                       num_classes);
                        since_last.cur_features = result[0].n;

                        if (predict)
                          { 
                            *predict << since_last.cur_predict;
          
                            for (unsigned int k = 0; k + 1 < num_classes; ++k)
                              { 
                                *predict << "\t" << result[k].p;
                              }
          
                            if ((*rvec)[n]->tag)
                              { 
                                *predict << "\t" << (*rvec)[n]->tag;
                              }
          
                            *predict << std::endl;
                          }

                        if ((*rvec)[n]->cost)
                          { 
                            SparseVector& cost (*(*rvec)[n]->cost);
          
                            since_last.loss += cost[since_last.cur_predict];
                            since_last.zo_loss +=
                              (since_last.cur_predict == *since_last.cur_label)
                                ? 0 : 1;
                            ++confusion_matrix[since_last.cur_predict][*since_last.cur_label];
                            for (unsigned int k = 0; k < num_classes; ++k)
                              { 
                                since_last.constant_loss[k] += cost[k];
                                since_last.constant_zo_loss[k] +=
                                  (k == *since_last.cur_label) ? 0 : 1;
                              }
                          }

                        if (since_last.example_count > (1ULL << output_number))
                          { 
                            cumulative += since_last;
              
                            if (report)
                              { 
                                do_report (*report, since_last, cumulative);
                              }
              
                            since_last.reset ();
              
                            ++output_number;
                          }

                        delete (*rvec)[n]->usecount;
                        delete (*rvec)[n]->estimationcount;
                        delete (*rvec)[n]->adaptivecount;
                        delete (*rvec)[n]->updatecount;
                      }
                  }

                delete rvec;
              }

            cumulative += since_last;

            if (report)
              { 
                do_report (*report, since_last, cumulative);
                loss.report (*report, Loss::WARN);
          
                if (cumulative.example_count > 0)
                  { 
                    unsigned int best = argmin (cumulative.constant_loss,
                                                num_classes);
                    unsigned int best_zo = argmin (cumulative.constant_zo_loss,
                                                   num_classes);
          
                    std::cerr << "best constant = " << best << std::endl;
                    std::cerr 
                      << "best constant loss = "
                      << static_cast<double> (cumulative.constant_loss[best]) /
                         static_cast<double> (cumulative.example_count)
                      << std::endl;
          
                    std::cerr << "best constant 0/1 = " << best_zo << std::endl;
                    std::cerr 
                      << "best constant 0/1 loss = "
                      << static_cast<double> (cumulative.constant_zo_loss[best_zo]) /
                         static_cast<double> (cumulative.example_count)
                      << std::endl;
          
                    std::cerr 
                      << "confusion matrix (rows=prediction,columns=actual)" 
                      << std::endl;
          
                    for (unsigned int m = 0; m < num_classes; ++m)
                      { 
                        for (unsigned int n = 0; n < num_classes; ++n)
                          { 
                            std::cerr << confusion_matrix[m][n] << "\t";
                          }
          
                        std::cerr << std::endl;
                      }
                  }
              }
          }
    };
}

#endif // __SFT_REPORT_WORKER_HH__
